/******************************************************************************
 *  _____     _____
 *  \  __\    \  __\
 *   \ \_\\    \ \_\\
 *    \  __\SS  \  __\roject
 *     \_\       \_\
 *
 * Mateusz Maciąg, Rafał Urbański
 * Komputerowe Systemy Sterowania
 *****************************************************************************/

#ifndef DISCRETEOBJECT_H
#define DISCRETEOBJECT_H

#include "SimInterface/IDiscreteObject.h"
#include "DiscreteObjectParams.h"
#include "Polynomial.h"
#include "SISOModel.h"
#include "UniformNoiseGenerator.h"
#include "Jano/NonlinearityBase.h"

#include <deque>
#include <queue>

#include <boost/smart_ptr.hpp>

// Przestrzeń nazw
namespace RafMat
{
namespace SimEngine
{

// Deklaracja zapowiadająca
class DiscreteObjectParams;

/**
    Alias na zręczny wskaźnik dla DiscreteObject.
*/
typedef boost::shared_ptr<DiscreteObject> DiscreteObjectSPtr;

/**
    Klasa reprezentująca obiekt dyskretny wg modelu ARX.

    Ogólna transmitancja obiektu ARX to: y = z^-n * B(z^-1)/A(z^-1)*u(z) + 1/A(z^-1)*e(z),
    gdzie z^-n - opóźnienie n próbek, A i B - wielomiany, e - biały szum.

    Równanie we-wy opisujące ów model wynika wprost z transmitancji:
    y(i) = b0*u(i-n) + ... + bdB*u(i-n-dB) - a1*y(i-1) - ... adA*y(i-dA) + e(i).

    Klasa jest typu wejściowo-wyjściowego (SISOModel). Dodatkowo implementuje interfejs
    IDiscreteObject dostępny dla GUI. Umożliwia on modyfikację parametrów obiektu,
    które są zbyt złożone by ogarnąć je prostym interfejsem IConfigurable.
*/
class DiscreteObject : public SISOModel, public SimInterface::IDiscreteObject
{
public:
    /**
        Tworzy objekt dyskretny ARX.
        Określamy maksymalny rząd obiektu, co jest istotne przy modelowaniu
        obiektów niestacjonarnych. Rząd ten określa jak duże zostaną zdefiniowane
        bufory na próbki przeszłe. Podobnie określamy maksymalne opóźnienie
        jakie obiekt jest wstanie "obsłużyć". Domyślne parametry pozwa

        \param [in] simulator Obiekt symulatora, w którym stworzono ten układ.
        \param [in] aMaxRank Maksymalny rząd mianownika transmitancji dyskretnej.
        \param [in] bMaxRank Maksymalny rząd licznika transmitancji dyskretnej.
        \param [in] maxDelay Maksymalne opóźnienie jakie układ może wprowadzać do pętli
        symulacji.
    */
    explicit DiscreteObject(const SimInterface::ISimulator& simulator,
                            unsigned int aMaxRank = 1,
                            unsigned int bMaxRank = 1,
                            unsigned int cMaxRank = 1,
                            unsigned int agMaxRank = 1,
                            unsigned int d = 0,
                            unsigned int delay = 0);

    /**
        Wykonuje jeden krok symulacji obiektu.

        Implementacja wymagana przez klasę bazową SISOModel. Realizuje całą dynamikę
        obiektu ARX zwracając jednocześnie jego aktualne wyjście.

        \param [in] input Wartość wejścia obiektu podawana z zewnątrz.
        \return Wartość wyjścia obiektu ARX.
    */
    virtual double simulateStep(double input);

    /**
        Resetuje obiekt, czyszcząc jego pamięć próbek.
        Domyślnie pamięć wypełniana jest zerami, czyli obiekt zostaje
        sprowadzony do stanu w jakim został utworzony.
        Metoda ta nie modyfikuje zestawów parametrów obiektu, ale umożliwia
        ich powtórne użycie ze względu na zresetowany licznik czasu (dotyczy
        modeli niestacjonarnych).
    */
    void reset();

    /**
        Resetuje obiekt przypisując mu warunki początkowe użytkownika.
        O ile zwykła funkcja reset() zadaje zerowe warunki początkowe,
        o tyle tutaj jest możliwość swobodnej inicjalizacji bufora próbek.
        Funkcja jest zabezpieczona przed błędnymi wymiarami wektorów u0 i y0.
        Jeśli podane wektory są za krótkie, to brakujące elementy zostaną
        wypełnione zerami. Dla dłuższych wektorów, nadmiarowe elementy zostaną
        odrzucone.

        Wektory u0 i y0 reprezentują chwile przeszłe od danej chwili (zerowej
        lub jakiejkolwiek innej).
        \code
        u0 = [ u(i) u(i-1) u(i-2) ... ]
        y0 = [ y(i-1) y(i-2) ... ]
        \endcode
    */
    void reset(const std::vector<double> &u0, const std::vector<double> &y0, const std::vector<double>& e0);


    void setNonlinearity(NonlinearityType nlType);

    /**
        Dodaje do obiektu zestaw parametrów na podstawie gotowego obiektu parametrów.

        \param [in] dop Zestaw parametrów, który chcemy przypisać obiektowi.
        Podajemy go poprzez zręczny wskaźnik do dynamicznego zasobu.
    */
    void insertParameters(DiscreteObjectParamsSPtr dop);

    /**
        Dodaje nowy zestaw parametrów na podstawie podanych wielomianów, opóźnień, etc.
        Należy zwrócić uwagę, że dodanie nowego zestawu parametrów nie powoduje
        automatycznego posortowania listy parametrów co jest wymagane przez algorytm
        'niestacjonarności' obiektu.

        \param [in] a Mianownik transmitancji.
        \param [in] b Licznik transmitancji.
        \param [in] delay Opóźnienie transportowe obiektu (w próbkach).
        \param [in] switchTime Moment przełączenia parametrów.
    */
    void insertParameters(Polynomial a, Polynomial b, Polynomial c, Polynomial ag, double d = 0, unsigned int delay = 0, int switchTime = -1);

    //
    // Implementacja interfejsu IDiscreteObject:
    //

    /**
        Dodaje nowy zestaw parametrów na podstawie podanych wielomianów, opóźnień, etc.
        Należy zwrócić uwagę, że dodanie nowego zestawu parametrów nie powoduje
        automatycznego posortowania listy parametrów co jest wymagane przez algorytm
        'niestacjonarności' obiektu. Funkcja implementuje interfejs IDiscreteObject
        i jest widziana przez GUI.

        \param [in] a Mianownik transmitancji (wektor standardowy).
        \param [in] b Licznik transmitancji (wektor standardowy).
        \param [in] delay Opóźnienie transportowe obiektu (w próbkach).
        \param [in] switchTime Moment przełączenia parametrów.

        \return Zręczny wskaźnik do obiektu utworzonego na podstawie podanych
        parametrów. Obiekt ten jest konfigurowalny z poziomu GUI.
    */
    SimInterface::IConfigurableSPtr insertParameters(std::vector<double> a,
                                                     std::vector<double> b,
                                                     std::vector<double> c,
                                                     std::vector<double> ag,
                                                     double d = 0,
                                                     unsigned int delay = 0,
                                                     int switchTime = -1);

    /**
        Usuwa daną konfigurację obiektu z listy parametrów.
        Parametry do usunięcia wyszukiwane są po wskaźniku.
        Funkcja wykonuje się bezpiecznie, usunięcie nieistniejącego wskaźnika
        nie zmienia stanu obiektu.

        \param [in] paramSet Zbiór parametrów obiektu, kótry chcemy usunąć.
    */
    void deleteParameters(SimInterface::IConfigurableSPtr paramSet);

    /**
        Zwraca wektor z wszystkimi zestawami parametrów zarejestrowanymi w obiekcie.

        \return Wektor standardowy ze zręcznymi wskaźnikami do obiektów konfiguracyjnych.
        Każdy z obiektów konfiguracyjnych odpowiada zestawowi parametrów.
    */
    std::vector<SimInterface::IConfigurableSPtr> getParameters() const;

    /**
        Przygotowuje zestawy parametrów do poprawnej pracy podczas symulacji.
    */
    void updateParameters();

    //
    // Implementacja interfejsu IConfigurable.
    //
    bool setParameter(const std::string&, const std::string&) throw(SimInterface::iconf_error);
    std::string getParameter(const std::string& param) const throw(SimInterface::iconf_error);

    SimFile::ConfigWriter& dumpParameters(SimFile::ConfigWriter& o) const;

    /**
        Zwraca maksymalny rząd mianownika transmitancji obiektu ARX.

        \return Maksymalny rząd mianownika podany w chwili tworzenia obiektu.
    */
    int getAMaxRank() const { return m_aMaxRank; }

    /**
        Zwraca maksymalny rząd licznika transmitancji obiektu ARX.

        \return Maksymalny rząd licznika podany w chwili tworzenia obiektu.
    */
    int getBMaxRank() const { return m_bMaxRank; }

    /**
        Zwraca maksymalny rząd wielomianu C stojacego przy e(i).

        \return Maksymalny rząd wielomianu C stojacego przy e(i) podany w chwili tworzenia obiektu.
    */
    int getCMaxRank() const { return m_cMaxRank; }

    /**
        Zwraca maksymalny rząd wielomianu Ag stojacego przy delta(i).

        \return Maksymalny rząd wielomianu Ag stojacego przy delta(i) podany w chwili tworzenia obiektu.
    */
    int getAgMaxRank() const { return m_agMaxRank; }

    /**
        Zwraca maksymalne d szumu deterministycznego.

        \return Maksymalne d szumu deterministycznego.
    */
    int getMaxD() const { return m_maxD; }

    /**
        Zwraca maksymalne opóźnienie transportowe obiektu ARX.

        \return Maksymalne opóźnienie transportowe podane w chwili tworzenia obiektu.
    */
    int getMaxDelay() const { return m_maxDelay; }

    /**
        Włącza/wyłącza symulację szumu losowego.
    */
    void enableNoise(bool en) { m_noiseEnabled = en; }

    /**
        Zwraca wektor próbek przeszłych u(i) o zadanej liczbie elementów.
        Wektor składa się z próbek [u(i) u(i-1) ...] i jest ich \c num.
    */
    std::vector<double> getPastInputSamples(int num);

    /**
        Zwraca wektor próbek przeszłych y(i) o zadanej liczbie elementów.
        Wektor składa się z próbek [y(i-1) y(i-2) ...] i jest ich \c num.
    */
    std::vector<double> getPastOutputSamples(int num);

private: 

    static const std::string CONF_MAX_ADEGREE;
    static const std::string CONF_MAX_BDEGREE;
    static const std::string CONF_MAX_CDEGREE;
    static const std::string CONF_MAX_AGDEGREE;
    static const std::string CONF_MAX_DELAY;
    static const std::string CONF_MAX_D;
    //static const std::string CONF_OUTNL_ENABLED;
    //static const std::string CONF_OUTNL_TYPE;

    /**
        Przygotowuje obiekt do symulacji.

        Funkcja wywoływana jest raz na etapie konstrukcji obiektu.
        Przygotowuje pamięć pod próbki przeszłe oraz wpisuje startowy
        zestaw parametrów.
    */
    void initialize();

    void resizeMemories();

    /**
        Zwraca zestaw parametrów aktualny dla najbliższego okresu próbkowania.

        Funkcja zakłada, że lista parametrów jest już posortowana.
        Funkcja zwraca następny zestaw parametrów modelu niestacjonarnego.
        Parametry zmieniane są skokowo po przekroczeniu odpowiedniego progu czasowego.

        Algorytm jest następujący:
        -# Sprawdź, czy lista zestawów jest pusta.
            # TAK: zwróć domyślny zestaw \c mc_defaultParams.
        -# Sprawdź, czy to ostatni zestaw z listy.
            # TAK: zwróć go, bo i tak późniejszych nie ma.
        -# Sprawdź, czy minął czas przełączenia w następnej próbce.
            # TAK: zwróć następny zestaw parametrów i zwiększ indeks.
            # NIE: zwróć aktualny zestaw parametrów i nie zwiększaj indeksu.

        \param [out] Flaga pokazująca, czy dokonano podmiany parametrów w bieżącym cyklu.
        Można wykorzystać do optymalizacji niektórych czynności.
        \return Aktualny zestaw parametrów, z których korzysta funkcja simulateStep.
    */
    DiscreteObjectParamsSPtr getNextParams(bool& changed);

    /** Cache dla domyślnych parametrów układu. */
    const DiscreteObjectParamsSPtr mc_defaultParams;

    /** Pojemnik na zestawy parametrów obiektu. */
    std::vector<DiscreteObjectParamsSPtr> m_modelParams;

    /** Indeks wskazujący na aktualny zestaw parametrów. */
    unsigned int m_currentMPIndex;

    int m_aMaxRank;                     ///< Maksymalny rząd mianownika transmitancji.
    int m_bMaxRank;                     ///< Maksymalny rząd licznika transmitancji.
    int m_cMaxRank;                     ///< Maksymalny rząd wielomianu C zwiazanego z e(i)
    int m_agMaxRank;                    ///< Maksymalny rząd wielomianu Ag zwiazanego z delta(i)
    unsigned int m_maxD;                ///< Maksymalne d.
    unsigned int m_maxDelay;            ///< Maksymalne opóźnienie.

    std::deque<double> m_outmem;        ///< Pamięć dla próbek wyjściowych.
    std::deque<double> m_inmem;         ///< Pamięć dla próbek wejściowych.
    std::deque<double> m_emem;         ///< Pamięć dla szumu
    std::deque<double> m_ddoutmem;         ///< Pamięć dla szumu

    bool m_noiseEnabled;                ///< Flaga, true - jeśli ma być symulowany szum;

    UniformNoiseGenerator m_noiseGen;   ///< Generator białego szumu.

    //bool m_nonlinearityEnabled;         ///< true - nieliniowość podpięta, false - bypass
    //NonlinearityBaseSPtr m_outputNL;    ///< Wyjściowa nieliniowośc
};

}   // SimEngine namespace
}   // RafMat namespace

#endif // DISCRETEOBJECT_H
